package com.stox.shared.beans;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.stox.shared.beans.annotation.Property;
import com.stox.shared.i18n.MessageSource;
import com.stox.shared.util.StringUtil;

public class BeanDescriptor extends AbstractDescriptor {

	private final List<PropertyDescriptor> propertyDescriptors = new ArrayList<PropertyDescriptor>();

	public BeanDescriptor() {

	}

	public BeanDescriptor(final Object bean) {
		if (null != bean) {
			introspect(bean);
		}
	}

	public Object createBean() {
		try {
			final Object bean = getType().newInstance();
			populateBean(bean);
			return bean;
		} catch (InstantiationException | IllegalAccessException | SecurityException | IllegalArgumentException exception) {
			exception.printStackTrace();
			throw new RuntimeException(exception);
		}
	}

	public void populateBean(final Object bean) {
		for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
			Method setter = null;
			try {
				setter = bean.getClass().getMethod("set" + propertyDescriptor.getBaseName(), new Class[] {propertyDescriptor.getType()});
				setter.setAccessible(true);
				setter.invoke(bean, propertyDescriptor.getValue());
			} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public List<PropertyDescriptor> getPropertyDescriptors() {
		return propertyDescriptors;
	}

	public void introspect(final Object bean) {
		setType(bean.getClass());
		final Method[] methods = bean.getClass().getMethods();
		for (final Method method : methods) {
			if (!Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers())) {
				method.setAccessible(true);
				PropertyDescriptor propertyDescriptor = null;
				if ((3 < method.getName().length()) && method.getName().startsWith("get") && (0 == method.getParameterCount()) && !Object.class.equals(method.getDeclaringClass()) && !void.class.equals(method.getReturnType())) {
					/**
					 * Simple getter
					 */
					try {
						propertyDescriptor = new PropertyDescriptor(method.getReturnType(), method.invoke(bean, new Object[0]), method.getName().substring(3), StringUtil.splitCamelCase(method.getName().substring(3)));
					} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
						exception.printStackTrace();
					}
				} else if ((2 < method.getName().length()) && method.getName().startsWith("is") && (0 == method.getParameterCount()) && boolean.class.equals(method.getReturnType())) {
					/**
					 * boolean getter
					 */
					try {
						propertyDescriptor = new PropertyDescriptor(boolean.class, method.invoke(bean, new Object[0]), method.getName().substring(2), StringUtil.splitCamelCase(method.getName().substring(2)));
					} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
						exception.printStackTrace();
					}
				}

				if (null != propertyDescriptor) {
					processAnnotations(method, propertyDescriptor);
					propertyDescriptors.add(propertyDescriptor);
				}

				Collections.sort(propertyDescriptors, PropertyDescriptorIndexComparator.getInstance());

			}
		}
	}

	private void processAnnotations(final Method method, final PropertyDescriptor propertyDescriptor) {
		final Property property = method.getAnnotation(Property.class);
		if (null != property) {
			propertyDescriptor.setHidden(property.hidden());
			propertyDescriptor.setIndex(property.index());
			if (StringUtil.hasText(property.displayNameKey())) {
				propertyDescriptor.setDisplayName(MessageSource.get(property.displayNameKey()));
			}
			if (StringUtil.hasText(property.descriptionKey())) {
				propertyDescriptor.setDescription(MessageSource.get(property.descriptionKey()));
			}
		}
	}

	@Override
	public String toString() {
		return "BeanDescriptor [propertyDescriptors=" + propertyDescriptors + ", toString()=" + super.toString() + "]";
	}
}
